﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace SharpDepend.Datatypes
{
    [StructLayout(LayoutKind.Explicit)]
    public struct Color
    {
        private static int mZ = (int)DateTime.Now.Ticks;
        private static int mW = (int)DateTime.Now.Ticks;

#if HardwareSaftey
        public byte R
        {
            get
            {
                return (byte)this.Packet;
            }
            set
            {
                this.Packet = (this.Packet & 0xffffff00) | value;
            }
        }

        public byte G
        {
            get
            {
                return (byte)(this.Packet >> 8);
            }
            set
            {
                this.Packet = (this.Packet &  0xffff00ff) | ((uint)(value << 8));
            }
        }

        public byte B
        {
            get
            {
                return (byte)(this.Packet >> 16);
            }
            set
            {
                this.Packet = (this.Packet & 0xff00ffff) | ((uint)(value << 16));
            }
        }

        public byte A
        {
            get
            {
                return (byte)(this.Packet >> 24);
            }
            set
            {
                this.Packet = (this.Packet & 0x00ffffff) | ((uint)(value << 24));
            }
        }

        public uint Packet;
#else
        [FieldOffset(0)]
        public byte R;

        [FieldOffset(1)]
        public byte G;

        [FieldOffset(2)]
        public byte B;

        [FieldOffset(3)]
        public byte A;

        [FieldOffset(0)]
        public uint Packet;

        [FieldOffset(0)]
        public int PacketSigned;
#endif

        public Color(int packet)
        {
            PacketSigned = 0;
            R = G = B = A = 0;
            Packet = (uint)packet;
        }

        public Color(uint packet)
        {
            PacketSigned = 0;
            R = G = B = A = 0;
            Packet = packet;
        }

        public static implicit operator Color(uint color)
        {
            return new Color(color);
        }

        public Color(byte r, byte g, byte b)
        {
            PacketSigned = 0;
            Packet = 0;
            G = g;
            B = b;
            R = r;
            A = 255;
        }

        public Color(byte a, byte r, byte g, byte b)
        {
            PacketSigned = 0;
            Packet = 0;
            R = r;
            G = g;
            B = b;
            A = a;
        }

        public Color(int a, int r, int g, int b)
        {
            PacketSigned = 0;
            Packet = 0;
            R = (byte)r;
            G = (byte)g;
            B = (byte)b;
            A = (byte)a;
        }

        public Color(double r, double g, double b)
        {
            PacketSigned = 0;
            Packet = 0;
            R = (byte)(r * 255);
            G = (byte)(g * 255);
            B = (byte)(b * 255);
            A = 255;
        }

        public Color(double r, double g, double b, double a)
        {
            PacketSigned = 0;
            Packet = 0;
            R = (byte)(r * 255);
            G = (byte)(g * 255);
            B = (byte)(b * 255);
            A = (byte)(a * 255);
        }

        public Color(Vector4 vector)
        {
            PacketSigned = 0;
            Packet = 0;
            R = (byte)(vector.X2 * 255.0);
            G = (byte)(vector.Y2 * 255.0);
            B = (byte)(vector.X1 * 255.0);
            A = (byte)(vector.Y1 * 255.0);
        }

        public Vector4 ToVector4()
        {
            return new Vector4(A / 255.0, R / 255.0, G / 255.0, B / 255.0);
        }

        public static Color operator -(Color left, Color right)
        {
            Color copy = left;
            copy.R -= right.R;
            copy.G -= right.G;
            copy.B -= right.B;
            copy.A -= right.A;
            return copy;
        }

        public static Color operator +(Color left, Color right)
        {
            Color copy = left;
            copy.R += right.R;
            copy.G += right.G;
            copy.B += right.B;
            copy.A += right.A;
            return copy;
        }

        public static Color operator *(Color left, Color right)
        {
            Color copy = left;
            copy.R *= right.R;
            copy.G *= right.G;
            copy.B *= right.B;
            copy.A *= right.A;
            return copy;
        }

        public static Color operator *(Color left, double right)
        {
            Color copy = left;
            copy.R = (byte)(Math.Min(255, copy.R * right));
            copy.G = (byte)(Math.Min(255, copy.G * right));
            copy.B = (byte)(Math.Min(255, copy.B * right));
            copy.A = (byte)(Math.Min(255, copy.A * right));
            return copy;
        }

        public static Color operator /(Color left, double right)
        {
            Color copy = left;
            copy.R = (byte)(copy.R / right);
            copy.G = (byte)(copy.G / right);
            copy.B = (byte)(copy.B / right);
            copy.A = (byte)(copy.A / right);
            return copy;
        }

        public static readonly Color Black = new Color(255, 0, 0, 0);
        public static readonly Color Gray = new Color(255, 127, 127, 127);
        public static readonly Color DarkGray = new Color(255, 55, 55, 55);
        public static readonly Color Red = new Color(255, 255, 0, 0);
        public static readonly Color Green = new Color(255, 0, 255, 0);
        public static readonly Color Blue = new Color(255, 0, 0, 255);
        public static readonly Color LightBlue = new Color(255, 173, 216, 230);
        public static readonly Color White = new Color(255, 255, 255, 255);
        public static readonly Color Pink = new Color(255, 255, 192, 203);
        public static readonly Color LightSkyBlue = new Color(255, 135, 206, 250);
        public static readonly Color Orange = new Color(255, 255, 96, 0);
        public static readonly Color Transparent = new Color(0, 0, 0, 0);

        public Color Normalized
        {
            get
            {
                double max = Math.Max(Math.Max(R, G), B);
                double diffPercent = 255.0 / max;
                return new Color((byte)(R * diffPercent), (byte)(G * diffPercent), (byte)(B * diffPercent));
            }
        }

        public Color Stretch
        {
            get
            {
                Color color = Normalized;

                if (color.R == 255)
                {
                    // GB
                    if (color.B < color.G)
                    {
                        double procent = ProcentMiddle(color.B, 255, color.G);
                        color.B = 0;
                        color.G = (byte)(255 * procent);
                    }
                    else
                    {
                        double procent = ProcentMiddle(color.G, 255, color.B);
                        color.G = 0;
                        color.B = (byte)(255 * procent);
                    }
                }
                else if (color.G == 255)
                {
                    // RB
                    if (color.B < color.R)
                    {
                        double procent = ProcentMiddle(color.B, 255, color.R);
                        color.B = 0;
                        color.R = (byte)(255 * procent);
                    }
                    else
                    {
                        double procent = ProcentMiddle(color.R, 255, color.B);
                        color.R = 0;
                        color.B = (byte)(255 * procent);
                    }
                }
                else if (color.B == 255)
                {
                    // RG
                    if (color.R < color.G)
                    {
                        double procent = ProcentMiddle(color.R, 255, color.G);
                        color.R = 0;
                        color.G = (byte)(255 * procent);
                    }
                    else
                    {
                        double procent = ProcentMiddle(color.G, 255, color.R);
                        color.G = 0;
                        color.R = (byte)(255 * procent);
                    }
                }

                return color;
            }
        }

        double ProcentMiddle(double min, double max, double value)
        {
            double diff = max - min;

            double value2 = value - min;

            double output = value2 / diff;

            return output;
        }

        /// <summary>
        /// Fast and bad random algorithm.
        /// <para>Reference: http://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation </para>
        /// </summary>
        /// <returns>Random color.</returns>
        public static Color Random()
        {
            return Random(255);
        }

        /// <summary>
        /// Fast and bad random algorithm.
        /// <para>Reference: http://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation </para>
        /// </summary>
        /// <returns>Random color.</returns>
        public static Color Random(byte alpha)
        {
            int z = mZ;
            int w = mW;
            z = 36969 * (z & 65535) + (z >> 16);
            w = 18000 * (w & 65535) + (w >> 16);
            int random = (z << 16) + w;
            mZ = z;
            mW = w;

            Color color = new Color(random);
            color.A = alpha;
            return color;
        }

        public Color Grayed(double scale)
        {
            if (scale < 0.0 || scale > 1.0)
            {
                throw new ArgumentOutOfRangeException("Scale can only be in range between 0.0 to 1.0");
            }

            double gray = (R + G + B) / 3.0;

            Color grayColor = new Color(
                (byte)(gray * scale + R * (1.0 - scale)),
                (byte)(gray * scale + G * (1.0 - scale)),
                (byte)(gray * scale + B * (1.0 - scale)));

            return grayColor;
        }
    }
}
